package me.yuxiaoyao.lock.annotation;

import me.yuxiaoyao.lock.interceptor.DistributedLockOperation;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.lang.Nullable;
import org.springframework.util.StringUtils;

import java.lang.annotation.Annotation;
import java.lang.reflect.AnnotatedElement;
import java.lang.reflect.Method;
import java.util.*;

/**
 * @author kerryzhang on 2020/12/22
 */


public class DefaultDistributedLockAnnotationParser implements DistributedLockAnnotationParser {
    private static final Set<Class<? extends Annotation>> LOCK_OPERATION_ANNOTATIONS = new LinkedHashSet<>(4);

    static {
        LOCK_OPERATION_ANNOTATIONS.add(DistributedLock.class);
        LOCK_OPERATION_ANNOTATIONS.add(DistributedLocks.class);
    }


    @Override
    public boolean isCandidateClass(Class<?> targetClass) {
        return AnnotationUtils.isCandidateClass(targetClass, LOCK_OPERATION_ANNOTATIONS);
    }

    @Override
    public Collection<DistributedLockOperation> parseDistributedLockAnnotations(Class<?> type) {
        return parseLockAnnotation(type);
    }

    @Override
    public Collection<DistributedLockOperation> parseDistributedLockAnnotations(Method method) {
        return parseLockAnnotation(method);
    }

    @Nullable
    private Collection<DistributedLockOperation> parseLockAnnotation(AnnotatedElement ae) {
        Collection<? extends Annotation> anns = AnnotatedElementUtils.getAllMergedAnnotations(ae, LOCK_OPERATION_ANNOTATIONS);
        if (anns.isEmpty()) {
            return null;
        }
        // 解析注解
        Collection<DistributedLockOperation> ops = new ArrayList<>(1);
        anns.stream().filter(ann -> ann instanceof DistributedLock).forEach(ann -> ops.add(parseLockAnnotation(ae, (DistributedLock) ann)));
        anns.stream().filter(ann -> ann instanceof DistributedLocks).forEach(ann -> ops.addAll(parseLocksAnnotation(ae, (DistributedLocks) ann)));
        // 多少个 @DistributedLock 注解，因为有 @DistributedLocks 的存在，所以会可能存在多个的情况
        return ops;
    }


    private List<DistributedLockOperation> parseLocksAnnotation(AnnotatedElement ae, DistributedLocks locks) {
        List<DistributedLockOperation> lockOperations = new ArrayList<>(locks.value().length);
        for (DistributedLock lock : locks.value()) {
            lockOperations.add(parseLockAnnotation(ae, lock));
        }
        return lockOperations;
    }


    private DistributedLockOperation parseLockAnnotation(AnnotatedElement ae, DistributedLock lock) {
        DistributedLockOperation.Builder builder = new DistributedLockOperation.Builder();
        DistributedLockOperation operation = builder
                .setName(ae.toString())
                .setKey(lock.value())
                .setBlock(lock.block())
                .setTimeout(lock.timeout())
                .setLockExecutor(lock.lockExecutor())
                .setKeyGenerator(lock.keyGenerator())
                .build();
        validateDistributedLockOperation(ae, operation);
        return operation;
    }

    /**
     * valid check
     *
     * @param ae
     * @param operation
     */
    private void validateDistributedLockOperation(AnnotatedElement ae, DistributedLockOperation operation) {
        if (StringUtils.hasText(operation.getKey()) && StringUtils.hasText(operation.getKeyGenerator())) {
            throw new IllegalStateException("Invalid distributed lock annotation configuration on '" +
                    ae.toString() + "'. Both 'value' and 'keyGenerator' attributes have been set. " +
                    "These attributes are mutually exclusive: either set the SpEL expression used to" +
                    "compute the key at runtime or set the name of the KeyGenerator bean to use.");
        }
    }

}
